#include "stdafx.h"
#include "Packages.h"
#include "XMLConnection.h"
#include "ServerConnection.h"
#include "CoreXMLHandler.h"
#include "CoreXMLHandlerManager.h"
#include "Logger.h"
#include "SafeMutex.h"

#include "Poco\Exception.h"
#include "Poco\Logger.h"
#include "Poco\DOM\DOMWriter.h"
#include "Poco\XML\XMLWriter.h"

namespace ExamSystem
{
    using Poco::Exception;
    using Poco::Logger;
    using Poco::Message;
    using Poco::Delegate;
    using Poco::XML::DOMWriter;
    using Poco::XML::XMLWriter;

    CXMLConnection::CXMLConnection(
        const StreamSocket& s,
        CCoreXMLHandlerManager *xmlManager,
        bool flagServerConnection /*= true*/
        )
        : m_socket(s)
        , m_flagKeepAlivePackageSent(false)
        , m_flagStreamStart(false)
        , m_localID(-1)
        , m_flagServerConnection(flagServerConnection)
        , m_flagForceClosed(false)
        , m_flagStreamStartLabelSent(false)
        , m_flagStreamEndLabelSent(false)
        , synStreamStart(false)
    {
        m_pISocketStream = new SocketInputStream(m_socket);
        m_pOSocketStream = new SafeSocketOutputStream(m_socket);
        m_pInputSource = new InputSource(*m_pISocketStream);
        m_pXMLHandler = xmlManager->CreateHandler();
        m_pXMLHandler->AttachToConnection(this);

        m_pXMLHandler->RegisterStanzaHandler(CKeepAlivePackage::packageType, this);
        m_pXMLHandler->RegisterStanzaHandler(CKeepAliveReplyPackage::packageType, this);

        m_pXMLHandler->eventInvalidStream += Delegate<CXMLConnection, int>(this, &CXMLConnection::OnInvalidStream);
        m_pXMLHandler->eventStreamStart += Delegate<CXMLConnection, int>(this, &CXMLConnection::OnStreamStart);
        m_pXMLHandler->eventStreamEnd += Delegate<CXMLConnection, int>(this, &CXMLConnection::OnStreamEnd);

        m_pParser = new SAXParser;
        m_pWhitespaceFilter = new WhitespaceFilter(m_pParser);
        // set up xml parser and start
        m_pParser->setEncoding("UTF-8");
        m_pParser->setFeature(SAXParser::FEATURE_PARTIAL_READS, true);
        m_pWhitespaceFilter->setContentHandler(m_pXMLHandler);
        m_pWhitespaceFilter->setFeature(SAXParser::FEATURE_PARTIAL_READS, true);
    }

    CXMLConnection::~CXMLConnection(void)
    {
        m_pXMLHandler->eventInvalidStream -= Delegate<CXMLConnection, int>(this, &CXMLConnection::OnInvalidStream);
        m_pXMLHandler->eventStreamStart -= Delegate<CXMLConnection, int>(this, &CXMLConnection::OnStreamStart);
        m_pXMLHandler->eventStreamEnd -= Delegate<CXMLConnection, int>(this, &CXMLConnection::OnStreamEnd);
    }

    void CXMLConnection::run(void)
    {
        CSafeScopedLock scope(m_runningLock);

        try
        {
            m_pWhitespaceFilter->parse(m_pInputSource);
        }
        catch (Poco::Exception& e)
        {
            CSafeScopedLock scope2(m_forceClosedFlagLock);
            if (!m_flagForceClosed)
                systemLogger.error(e.displayText());
        }

        // here the socket must be closed
        eventConnectionClosed(this, m_localID);
    }

    void CXMLConnection::startElement(CXMLConnection *connection, const XMLString& uri, const XMLString& localName, const XMLString& qname, const Attributes& attributes)
    {
        m_packageType = attributes.getValue(CPackage::namespaceURI, CPackage::attributeType);
    }

    void CXMLConnection::endElement(CXMLConnection *connection, const XMLString& uri, const XMLString& localName, const XMLString& qname)
    {
        if (localName == CPackage::labelPackage)
        {
            if (m_packageType == CKeepAlivePackage::packageType)
            {
                // for hold package, we should send a reply
                packageManager.CreatePackage(CKeepAliveReplyPackage::packageType)->WriteToStream(m_pOSocketStream);
                //SendRawByte(CPackage::packageKeepAliveReply.data(), CPackage::packageKeepAliveReply.length());
                //CExamProtocol::SendKeepAliveReplyPackage(*m_pOSocketStream);
            }
            else if (m_packageType == CKeepAliveReplyPackage::packageType)
            {
                CSafeScopedLock lock(m_lockKeepaliveFlag);
                m_flagKeepAlivePackageSent = false;
            }
        }
    }

    void CXMLConnection::characters(CXMLConnection *connection, const XMLChar ch[], int start, int length)
    {
        // do nothing
    }

    void CXMLConnection::OnInvalidStream(const void* pSender, int&)
    {
        systemLogger.warning("Invalid connection.");
        m_socket.close();
        synStreamStart.set();
    }

    void CXMLConnection::OnStreamStart(const void* pSender, int&)
    {
        CSafeScopedLock lock(m_lockStreamStartFlag);
        if (!m_flagStreamStartLabelSent)  // we have sent the label
        {
            SendRawByte(CPackage::capsuledLabelExamination.data(), CPackage::capsuledLabelExamination.length());
            m_flagStreamStartLabelSent = true;
        }

        m_flagStreamStart = true;
        synStreamStart.set();
    }

    void CXMLConnection::OnStreamEnd(const void* pSender, int&)
    {
        CSafeScopedLock lock(m_lockStreamStartFlag);
        // once get a /examination message, reply a /examination and close the socket
        if (m_flagStreamStart) 
        {
            SendRawByte(CPackage::capsuledLabelExaminationEnd.data(), CPackage::capsuledLabelExaminationEnd.length());
            //CExamProtocol::SendStreamEndLabel(*m_pOSocketStream);
            m_socket.close();
            m_flagStreamStart = false;
        }
        synStreamStart.set();
    }

    void CXMLConnection::SendKeepAlivePackage(void)
    {
        CSafeScopedLock lock(m_lockStreamStartFlag);
        // we don't send keep-alive package before a valid stream has been established.
        if (!m_flagStreamStart)
            return;

        // we don't send new keep alive package before last one has been replied
        if (m_flagKeepAlivePackageSent)
            return;

        packageManager.CreatePackage(CKeepAlivePackage::packageType)->WriteToStream(m_pOSocketStream);
        //SendRawByte(packageKeepAlive.data(), packageKeepAlive.length());
        //CExamProtocol::SendKeepAlivePackage(*m_pOSocketStream);
        m_flagKeepAlivePackageSent = true;
    }

    bool CXMLConnection::IsKeepAlivePackageReplied(void)
    {
        CSafeScopedLock lock(m_lockKeepaliveFlag);
        return !m_flagKeepAlivePackageSent;
    }

    bool CXMLConnection::SendRawByte(const void *data, int length)
    {
        if (data == NULL)
            return true;

        int sentLength = 0;

        try {
            CSafeScopedLock lock(*m_pOSocketStream);
            while (sentLength < length)
            {
                int s = m_socket.sendBytes(reinterpret_cast<const char *>(data) + sentLength, length - sentLength);
                sentLength += s;
            }
        }
        catch (Exception& exc)
        {
            // net or i/o exception
            systemLogger.error(exc.displayText());
            return false;
        }
        return true;
    }

    void CXMLConnection::StartStream(void)
    {
        CSafeScopedLock lock(m_lockStreamStartFlag);
        if (m_flagStreamStart)
            return;

        SendRawByte(CPackage::capsuledLabelExamination.data(), CPackage::capsuledLabelExamination.length());
        //CExamProtocol::SendStreamStartLabel(*m_pOSocketStream);
        m_flagStreamStartLabelSent = true;
    }

    void CXMLConnection::EndStream(void)
    {
        CSafeScopedLock lock(m_lockStreamStartFlag);
        if (!m_flagStreamStart)
            return;

        SendRawByte(CPackage::capsuledLabelExaminationEnd.data(), CPackage::capsuledLabelExaminationEnd.length());
        //CExamProtocol::SendStreamEndLabel(*m_pOSocketStream);
        m_flagStreamStart = false;
    }

    bool CXMLConnection::IsStreamStarted(void)
    {
        CSafeScopedLock lock(m_lockStreamStartFlag);
        return m_flagStreamStart;
    }
    void CXMLConnection::CloseConnection(void)
    {
        CSafeScopedLock lock(m_lockStreamStartFlag);
        CSafeScopedLock lock2(m_forceClosedFlagLock);
        if (m_flagStreamStart)
            SendRawByte(CPackage::capsuledLabelExaminationEnd.data(), CPackage::capsuledLabelExaminationEnd.length());
        //CExamProtocol::SendStreamEndLabel(*m_pOSocketStream);
        m_flagStreamStart = false;

        try
        {
            m_socket.shutdown();
            m_socket.close();
        }
        catch (Exception &)
        {
            // do nothing
        }
        m_flagForceClosed = true;
        //FireConnectionClosedEvent();
    }

    bool CXMLConnection::SendXMLStanza(Document &xmlStanza)
    {
        DOMWriter writer;
        writer.setNewLine("\n");
        writer.setOptions(XMLWriter::PRETTY_PRINT);

        try
        {
            CSafeScopedLock lock(*m_pOSocketStream);
            writer.writeNode(*m_pOSocketStream, &xmlStanza);
            m_pOSocketStream->flush();
        }
        catch (Exception &exp)
        {
            systemLogger.error(exp.displayText());
            return false;
        }

        return true;
    }

    int CXMLConnection::GetLocalID(void) const
    {
        return m_localID;
    }

    SharedPtr<CCoreXMLHandler> & CXMLConnection::GetXMLHandler(void)
    {
        return m_pXMLHandler;
    }

    SharedPtr<SafeSocketOutputStream> &CXMLConnection::GetSocketOutputStream(void)
    {
        CSafeScopedLock lock(m_lockStreamStartFlag);
        if (!m_flagStreamStart)
        {
            static SharedPtr<SafeSocketOutputStream> nullPointer = NULL;
            return nullPointer;
        }
        return m_pOSocketStream;
    }

    void CXMLConnection::SetLocalID(int localID)
    {
        m_localID = localID;
    }

    void CXMLConnection::WaitForConnectionStop(void)
    {
        CSafeScopedLock scope(m_runningLock);
    }

    StreamSocket & CXMLConnection::GetSocket(void)
    {
        return m_socket;
    }


}